#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import threading
import time

from common.common_models import ActionResult, LogDetail
from common.const import ExecuteResultEnum, SubJobStatusEnum, RestoreTypeEnum, EnvInfoPrefix, DBLogLevel, ReportDBLabel
from common.logger import Logger
from common.number_const import NumberConst
from common.parse_parafile import get_env_variable
from db2.backup.util.db2_backup_util import Db2BackupUtil
from db2.comm.const import Db2JsonConstant
from db2.comm.db2_verification import check_database_exists
from db2.comm.error_code import Db2ErrCode
from db2.comm.util.dpf_util import DpfUtil
from db2.comm.util.job_decorators import job_exception_decorator
from db2.restore.db2_restore_base import Db2RestoreBase
from db2.comm.util.common_util import Db2CommonUtil
from db2.comm.util.param_util import Db2ParamUtil
from db2.restore.single.single_db_restore_service import SingleRestoreService
LOGGER = Logger().get_logger(filename="db2.log")


class SingleRestore(Db2RestoreBase):
    def __init__(self, task_name, pid, job_id, sub_job_id, param_dict):
        super().__init__(task_name, pid, job_id, sub_job_id, param_dict)
        self.restore_handle = SingleRestoreService(pid, job_id, sub_job_id, param_dict)
        self.log_detail = LogDetail(logInfo=ReportDBLabel.RESTORE_SUB_START_COPY,
                                    logInfoParam=[self.sub_job_id], logLevel=DBLogLevel.INFO.value)

    def allow_restore_in_local_node(self):
        os_name = get_env_variable(f'{Db2JsonConstant.JOB_TARGETENV_NODES_0_AUTH_AUTHKEY}_{self.pid}')
        db_name = Db2ParamUtil.get_db_name_when_restore_db(self.param_dict)
        # 检测数据库实例是否存在
        if not Db2CommonUtil.check_process_service(os_name):
            LOGGER.error("Process is not start!")
            return Db2CommonUtil.build_action_result(code=ExecuteResultEnum.INTERNAL_ERROR.value,
                                                     message="Process is not start!",
                                                     body_err=Db2ErrCode.DB_SERVICE_ERROR)
        # 检测数据库是否存在
        if not check_database_exists(os_name, db_name):
            LOGGER.error(f"User {os_name} database {db_name} not exists.")
            return Db2CommonUtil.build_action_result(
                code=ExecuteResultEnum.INTERNAL_ERROR.value,
                err_params=[db_name],
                message="Database not exists.")
        pending_ret, pending_info_str = DpfUtil.check_database_pending_status_for_restore(os_name, db_name)
        # 检查目标数据库状态是否正常
        if pending_ret:
            LOGGER.error("There are pending tasks in the db2 single database.")
            return Db2CommonUtil.build_action_result(
                code=ExecuteResultEnum.INTERNAL_ERROR.value, body_err=Db2ErrCode.DATABASE_EXISTS_PENDING,
                err_params=[pending_info_str], message="There are pending tasks in the db2 single database")

        LOGGER.info("Allow_backup_in_local_node end")
        return Db2CommonUtil.build_action_result(code=ExecuteResultEnum.SUCCESS.value)

    def exec_restore_pre_job(self):
        # 前置任务
        LOGGER.info(f"Start restore pre (database).")
        target_db_name = Db2ParamUtil.get_db_name_when_restore_db(self.param_dict)
        user_name = get_env_variable(f'{EnvInfoPrefix.NODE_RESTORE_USERNAME}_{self.pid}')

        if not Db2CommonUtil.get_db_loc_dir(user_name, target_db_name):
            LOGGER.error(f"The database or database directory does not exist. database_name: {target_db_name}")
            self.sub_job_detail.progress = NumberConst.HUNDRED
            self.sub_job_detail.task_status = SubJobStatusEnum.FAILED.value
            Db2CommonUtil.proactively_report_progress(pid=self.pid, job_detail=self.sub_job_detail)
            # 返回结果
            return Db2CommonUtil.build_action_result(
                code=ExecuteResultEnum.INTERNAL_ERROR.value,
                message="The database or database directory does not exist.")

        # 关闭所有数据库连接
        if not Db2CommonUtil.disconnect_db_application(user_name, target_db_name):
            LOGGER.info(f"Failed to disconnect from the database. dt_name: {target_db_name}")
        LOGGER.info(f"Running restore pre complete(database).")

        # 写进度文件
        self.sub_job_detail.progress = NumberConst.HUNDRED
        self.sub_job_detail.task_status = SubJobStatusEnum.COMPLETED.value
        Db2CommonUtil.proactively_report_progress(pid=self.pid, job_detail=self.sub_job_detail)

        # 返回状态
        return Db2CommonUtil.build_action_result(code=ExecuteResultEnum.SUCCESS.value)

    @job_exception_decorator(write_progress=True, is_restore=True)
    def exec_restore(self):
        cache_path = Db2ParamUtil.get_cache_mount_path_for_restore(self.param_dict)
        os_user = get_env_variable(f'{EnvInfoPrefix.NODE_RESTORE_USERNAME}_{self.pid}')
        copy_db_name = Db2ParamUtil.get_db_name_of_db_copy(self.param_dict)
        db_name = Db2ParamUtil.get_db_name_when_restore_db(self.param_dict)

        # 备份开始的初始进度为5%
        self.sub_job_detail.progress = NumberConst.FIVE
        self.sub_job_detail.task_status = SubJobStatusEnum.RUNNING.value
        self.sub_job_detail.log_detail = [self.log_detail]
        Db2CommonUtil.proactively_report_progress(pid=self.pid, job_detail=self.sub_job_detail)
        # 上报完开始执行子任务后将标签清理,恢复agent框架在备份加了子任务执行开始lable,恢复未加
        self.sub_job_detail.log_detail = []
        progress_thread = threading.Thread(name="progress", target=self.upload_report_progress)
        # 任务意外结束时子线程也结束任务
        progress_thread.daemon = True
        progress_thread.start()
        LOGGER.info(f"Start running restore(database).")
        fun_dict = {
            RestoreTypeEnum.FULL_RESTORE.value: self.restore_handle.full_restore,
            RestoreTypeEnum.DIFF_RESTORE.value: self.restore_handle.diff_restore,
            RestoreTypeEnum.INCRE_RESTORE.value: self.restore_handle.incr_restore,
            RestoreTypeEnum.LOG_RESTORE.value: self.restore_handle.log_restore
        }
        restore_type = Db2ParamUtil.get_restore_type(self.param_dict)
        restore_func = fun_dict.get(restore_type)
        if not restore_func:
            # 下发不支持的其他备份直接返回失败
            LOGGER.error(f"Running Restore type {restore_type} failed, pid: {self.pid}"
                         f"job_id: {self.job_id} sub_job_Id: {self.sub_job_id}")
            self.sub_job_detail.progress = NumberConst.HUNDRED
            self.sub_job_detail.task_status = SubJobStatusEnum.FAILED.value
            self.log_detail.log_info = ReportDBLabel.SUB_JOB_FALIED
            self.log_detail.log_level = DBLogLevel.ERROR.value
            self.sub_job_detail.log_detail = [self.log_detail]
            Db2CommonUtil.proactively_report_progress(pid=self.pid, job_detail=self.sub_job_detail)
            return Db2CommonUtil.build_action_result(code=ExecuteResultEnum.INTERNAL_ERROR.value)

        # 执行恢复
        Db2CommonUtil.check_injection(copy_db_name, db_name)
        restore_func(cache_path=cache_path, os_user=os_user, copy_db_name=copy_db_name, db_name=db_name)

        # 恢复完成子任务写入临时进度文件
        self.sub_job_detail.progress = NumberConst.HUNDRED
        self.sub_job_detail.task_status = SubJobStatusEnum.COMPLETED.value
        self.log_detail.log_info = ReportDBLabel.SUB_JOB_SUCCESS
        self.sub_job_detail.log_detail = [self.log_detail]
        Db2CommonUtil.proactively_report_progress(pid=self.pid, job_detail=self.sub_job_detail)
        return Db2CommonUtil.build_action_result(code=ExecuteResultEnum.SUCCESS.value)

    def exec_restore_post_job(self):
        # 恢复后置任务
        try:
            ret = self.restore_handle.restore_post_handle()
        except Exception as e_info:
            ret = False
            LOGGER.error(f"Running restore post exception, exp: {e_info} pid: {self.pid}"
                         f"job_id: {self.job_id} sub_job_Id: {self.sub_job_id}")
        if not ret:
            LOGGER.error(f"Running restore post failed, pid: {self.pid}"
                         f"job_id: {self.job_id} sub_job_Id: {self.sub_job_id}")
            self.sub_job_detail.progress = NumberConst.HUNDRED
            self.sub_job_detail.task_status = SubJobStatusEnum.FAILED.value
            Db2CommonUtil.proactively_report_progress(pid=self.pid, job_detail=self.sub_job_detail)
            return Db2CommonUtil.build_action_result(code=ExecuteResultEnum.INTERNAL_ERROR.value)

        self.sub_job_detail.progress = NumberConst.HUNDRED
        self.sub_job_detail.task_status = SubJobStatusEnum.COMPLETED.value
        Db2CommonUtil.proactively_report_progress(pid=self.pid, job_detail=self.sub_job_detail)
        return Db2CommonUtil.build_action_result(code=ExecuteResultEnum.SUCCESS.value)

    def report_progress(self):
        """
        上报进度
        """
        progress_file_name = self.sub_job_id if self.sub_job_id else self.job_id
        cache_path = Db2ParamUtil.get_cache_mount_path_for_restore(self.param_dict)
        Db2BackupUtil.report_progress_utl(self.pid, progress_file_name, cache_path)

    def output_action_result(self, action_ret: ActionResult):
        super(SingleRestore, self).output_action_result(action_ret)

    def allow_restore_in_local(self):
        pass

    def upload_report_progress(self):
        # 只关心运行中的任务
        while True:
            if self.sub_job_detail.task_status == SubJobStatusEnum.RUNNING.value:
                user_name = get_env_variable(f'{EnvInfoPrefix.NODE_RESTORE_USERNAME}_{self.pid}')
                db_name = Db2ParamUtil.get_db_name_when_restore_db(self.param_dict)
                progress = Db2CommonUtil.get_now_backup_process(user_name, db_name)
                # 防止前面任务未写进度的情况。
                if self.sub_job_detail.progress and self.sub_job_detail.progress < progress:
                    self.sub_job_detail.progress = progress
                Db2CommonUtil.proactively_report_progress(pid=self.pid, job_detail=self.sub_job_detail)
            else:
                LOGGER.debug(f"Stop report progress thread. current job status is : {self.sub_job_detail.dict()}")
                break
            time.sleep(NumberConst.TEN)
